/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <grp.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <glib.h>
#include "bsearch.h"
#include "paths.h"
typedef GSList *(*gslist_func)(gpointer list, gconstpointer data);
static gint slist_path_cmp(const gchar *a, const gchar *b)
{
	return strcmp(a, b);
}

static GSList *slist_path_add(GSList *list, gpointer data, gslist_func func)
{
	g_assert(func);
	if(!data)
		return list;
	if(!g_slist_find_custom(list, data, (GCompareFunc) slist_path_cmp))
		list = func(list, data);
	else
		g_free(data);
	return list;
}

static GSList *split_paths(const gchar *paths)
{
	GSList *list = NULL;
	gchar **spl, **it;
	if(!paths)
		return NULL;
	spl = g_strsplit(paths, ":", -1);
	for(it = spl; *it; ++it)
		list = slist_path_add(list, *it, (gslist_func)g_slist_append);
	g_free(spl);
	return list;
}

static int gid_cmp(const void *va, const void *vb)
{
	const gid_t a = *(const gid_t *)va, b = *(const gid_t *)vb;
	return a > b ? 1 : (a == b ? 0 : -1);
}

static void find_uid_gid(uid_t * u, gid_t ** g, guint *n)
{
	struct passwd *pw;
	const gchar *name;
	struct group *gr;
	*u = getuid();
	pw = getpwuid(*u);
	name = pw->pw_name;
	*g = g_new(gid_t, *n = 1);
	(*g)[0] = getgid();
	while((gr = getgrent())) {
		if(gr->gr_gid != (*g)[0]) {
			gchar **c;
			for(c = gr->gr_mem; *c; ++c)
				if(strcmp(*c, name) == 0) {
					*g = g_renew(gid_t, *g, ++(*n));
					(*g)[*n - 1] = gr->gr_gid;
					break;
				}
		}
	}
	endgrent();
	qsort(*g, *n, sizeof(gid_t), gid_cmp);
}

struct t_paths *t_paths_new(void)
{
	struct t_paths *p;
	const gchar *path;
	GSList *it;
	p = g_slice_new0(struct t_paths);
	p->ref = 1;
	find_uid_gid(&p->uid, &p->gid, &p->n_gid);
	path = g_getenv("XDG_CONFIG_HOME");
	if(path && path[0] != '\0')
		p->config_home = g_build_filename(path, NULL);
	else
		p->config_home = g_build_filename(g_get_home_dir(), ".config", NULL);
	path = g_getenv("XDG_DATA_HOME");
	if(path && path[0] != '\0')
		p->data_home = g_build_filename(path, NULL);
	else
		p->data_home = g_build_filename(g_get_home_dir(), ".local", "share", NULL);
	path = g_getenv("XDG_CACHE_HOME");
	if(path && path[0] != '\0')
		p->cache_home = g_build_filename(path, NULL);
	else
		p->cache_home = g_build_filename(g_get_home_dir(), ".cache", NULL);
	path = g_getenv("XDG_CONFIG_DIRS");
	if(path && path[0] != '\0')
		p->config_dirs = split_paths(path);
	else {
		p->config_dirs = slist_path_add(p->config_dirs, g_strdup(CONFIGDIR), (gslist_func)g_slist_append);
		p->config_dirs =
			slist_path_add(p->config_dirs, g_build_filename(G_DIR_SEPARATOR_S, "etc", "xdg", NULL),
			(gslist_func)g_slist_append);
	}
	p->config_dirs = slist_path_add(p->config_dirs, g_strdup(p->config_home), (gslist_func)g_slist_prepend);
	for(it = p->config_dirs; it; it = g_slist_next(it)) {
		gchar *const s = g_strdup_printf("%s/autostart", (gchar *)it->data);
		p->autostart_dirs = g_slist_append(p->autostart_dirs, s);
	}
	path = g_getenv("XDG_DATA_DIRS");
	if(path && path[0] != '\0')
		p->data_dirs = split_paths(path);
	else {
		p->data_dirs = slist_path_add(p->data_dirs, g_strdup(DATADIR), (gslist_func)g_slist_append);
		p->data_dirs =
			slist_path_add(p->data_dirs, g_build_filename(G_DIR_SEPARATOR_S, "usr", "local", "share", NULL),
			(gslist_func)g_slist_append);
		p->data_dirs =
			slist_path_add(p->data_dirs, g_build_filename(G_DIR_SEPARATOR_S, "usr", "share", NULL),
			(gslist_func)g_slist_append);
	}
	p->data_dirs = slist_path_add(p->data_dirs, g_strdup(p->data_home), (gslist_func)g_slist_prepend);
	path = g_getenv("PATH");
	if(path && path[0] != '\0')
		p->exec_dirs = split_paths(path);
	else
		p->exec_dirs = NULL;
	return p;
}

void t_paths_unref(struct t_paths *p)
{
	if(p && --p->ref == 0) {
		GSList *it;
		for(it = p->config_dirs; it; it = g_slist_next(it))
			g_free(it->data);
		g_slist_free(p->config_dirs);
		for(it = p->data_dirs; it; it = g_slist_next(it))
			g_free(it->data);
		g_slist_free(p->data_dirs);
		for(it = p->autostart_dirs; it; it = g_slist_next(it))
			g_free(it->data);
		g_slist_free(p->autostart_dirs);
		for(it = p->exec_dirs; it; it = g_slist_next(it))
			g_free(it->data);
		g_slist_free(p->exec_dirs);
		g_free(p->config_home);
		g_free(p->data_home);
		g_free(p->cache_home);
		g_free(p->gid);
		g_slice_free(struct t_paths, p);
	}
}

gchar *t_paths_expand_tilde(const gchar *f)
{
	gchar *ret;
	GRegex *regex;
	if(!f)
		return NULL;
	regex = g_regex_new("(?:^|(?<=[ \\t]))~(?=[/ \\t$])", G_REGEX_MULTILINE | G_REGEX_RAW, 0, NULL);
	ret = g_regex_replace_literal(regex, f, -1, 0, g_get_home_dir(), 0, NULL);
	g_regex_unref(regex);
	return ret;
}

gboolean t_paths_mkdir(const gchar *path, gint mode)
{
	gboolean ret = TRUE;
	g_return_val_if_fail(path != NULL, FALSE);
	g_return_val_if_fail(path[0] != '\0', FALSE);
	if(!g_file_test(path, G_FILE_TEST_IS_DIR))
		if(mkdir(path, mode) == -1)
			ret = FALSE;
	return ret;
}

gboolean t_paths_mkdir_path(const gchar *path, gint mode)
{
	gboolean ret = TRUE;
	g_return_val_if_fail(path != NULL, FALSE);
	g_return_val_if_fail(path[0] == '/', FALSE);
	if(!g_file_test(path, G_FILE_TEST_IS_DIR)) {
		gchar *c, *e;
		c = g_strdup(path);
		e = c;
		while((e = strchr(e + 1, '/'))) {
			*e = '\0';
			if(!(ret = t_paths_mkdir(c, mode)))
				goto parse_mkdir_path_end;
			*e = '/';
		}
		ret = t_paths_mkdir(c, mode);
	  parse_mkdir_path_end:
		g_free(c);
	}
	return ret;
}

const gchar *t_paths_cache_home(struct t_paths *p)
{
	return p->cache_home;
}

GSList *t_paths_config_dirs(struct t_paths *p)
{
	return p->config_dirs;
}

GSList *t_paths_data_dirs(struct t_paths *p)
{
	return p->data_dirs;
}

static inline gboolean try_exec(const struct t_paths *const p, const gchar *const path)
{
	struct stat st;
	BSEARCH_SETUP(guint);
	if(stat(path, &st) != 0)
		return FALSE;
	if(!S_ISREG(st.st_mode))
		return FALSE;
	if(st.st_uid == p->uid)
		return st.st_mode & S_IXUSR;
	BSEARCH(guint, p->gid, 0, p->n_gid, st.st_gid);
	if(BSEARCH_FOUND())
		return st.st_mode & S_IXGRP;
	return st.st_mode & S_IXOTH;
}
